package edu.pku.sei.ocl.utilities;


import java.util.Collection;
import java.util.List;

import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EObjectEList;
import org.eclipse.emf.ecore.util.EcoreEList;

import edu.pku.sei.ocl.cst.CollectionTypeIdentifierEnum;
import edu.pku.sei.ocl.evaluation.OCLVariable;
import edu.pku.sei.ocl.types.CollectionType;
import edu.pku.sei.ocl.types.TupleType;
import edu.pku.sei.ocl.types.TypesFactory;
import edu.pku.sei.ocl.types.TypesPackage;
import edu.pku.sei.ocl.types.UnlimitedNatural;
import edu.pku.sei.ocl.value.OCLBasicCollection;
import edu.pku.sei.ocl.value.OCLCollection;
import edu.pku.sei.ocl.value.OCLCollection;
import edu.pku.sei.ocl.value.OCLEcoreCollection;
import edu.pku.sei.ocl.value.OCLTuple;
import edu.pku.sei.ocl.value.OCLCollection.*;


public class OCLStandardLibrary{
	private static final String[] PRODUCETUPLESTRINGS = new String[]{"first","second"};

	static public boolean oclIsInvalid(Object self){
		return self==OCLConstant.OCLInvalid||self==null;
	}
	static public boolean oclIsUndefined(Object self){
		return self==OCLConstant.OCLInvalid||self==OCLConstant.OCLNull||self==null;
	}
	
	@SuppressWarnings("deprecation")
	static public boolean oclIsUninited(Object self){
		return self==OCLConstant.OCLUninited;
	}
	
	static public boolean oclIsValid(Object self){
		return !(oclIsUndefined(self) || oclIsUninited(self));
	}
	
	/**
	 * 
	 * @param self
	 * @return 0-valid, 1 invalid 2 unitited
	 */
	static public int preTest(Object self){
		if(oclIsUndefined(self))
			return 1;//invalid
//		else if(self==OCLConstant.OCLUninited)
//			return 2;//uninited
		else return 0;
	}
	
	static public int  preTest(Object a,Object b, Class<?>... types){
		int ia = preTest(a);
		int ib = preTest(b);
		
		if(ia==1||ib==1)
			return 1;
		
		if(ia==0){
			if(typeCheck(a,types)) {
				//if(ib==0){
					if(typeCheck(b,types))
						return 0;
					else return 1;
				//} else {//ib==2
				//	return 2;
				//}
			} else {
				return 1;
			}	
		} else { //ia==2
			//if(ib==0){
				if(typeCheck(b,types))
					return 2;
				else return 1;
			//} else { //ib==2
			//	return 2;
			//}
		}
	}
	static private boolean typeCheck(Object a, Class<?>...types){
		for(Class<?> c : types){
			if(c.isPrimitive()&&a.getClass()==c)
				return true;
			else if(!c.isPrimitive()&&c.isAssignableFrom(a.getClass())){
				return true;
			}
		}
		return false;
	}

	static public boolean isTrue(Object self){
		return Boolean.TRUE.equals(self);	
	}
	
	static public boolean isFalse(Object self){
		return Boolean.FALSE.equals(self);	
	}
	
	static public EClassifier oclType(Object self){		
		if(self==OCLConstant.OCLInvalid){
			return TypesPackage.eINSTANCE.getOclInvalid();
		} /*else if(self==OCLConstant.OCLUninited){
			return TypesPackage.eINSTANCE.getOclInvalid();
		}*/ else if(self==OCLConstant.OCLNull){
			return TypesPackage.eINSTANCE.getOclVoid();
		} else if(self==OCLConstant.OCLUnlimitedNatural){
			return TypesPackage.eINSTANCE.getUnlimitedNatural();
		}
		
		if(self instanceof String){
			return TypesPackage.eINSTANCE.getStringType();
		} else if(self instanceof Integer){
			return TypesPackage.eINSTANCE.getIntegerType();			
		} else if(self instanceof Double){
			return TypesPackage.eINSTANCE.getRealType();			
		}else if(self instanceof Boolean){
			return TypesPackage.eINSTANCE.getBooleanType();
		} else if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			switch(col.getCollectionType()){
			case Set:
				return TypesFactory.eINSTANCE.createSetType(col.getElementOclType());
			case Bag:
				return TypesFactory.eINSTANCE.createBagType(col.getElementOclType());
			case Sequence:
				return TypesFactory.eINSTANCE.createSequenceType(col.getElementOclType());
			case OrderedSet:
				return TypesFactory.eINSTANCE.createOrderedSetType(col.getElementOclType());
			case Collection:
				return TypesFactory.eINSTANCE.createCollectionType(col.getElementOclType());
			}
			return null;
			
		} else if(self instanceof OCLTuple){
			OCLTuple tuple = (OCLTuple)self;
			return TypesFactory.eINSTANCE.createTupleType(tuple.keyNames,tuple.valueOclTypes);
		} else if(self instanceof EObject){
			return ((EObject)self).eClass();
		}
		return TypesPackage.eINSTANCE.getOclAny();
	}
	
	static public boolean comformTo(EClassifier typeA, EClassifier typeB){
		if(typeA==typeB)
			return true;
		if(typeA==TypesPackage.eINSTANCE.getOclInvalid())
			return true;
		if(typeB==TypesPackage.eINSTANCE.getOclAny())
			return true;
		if(typeA==TypesPackage.eINSTANCE.getOclVoid()&&typeB!=TypesPackage.eINSTANCE.getOclInvalid())
			return true;
		
		if(typeA instanceof CollectionType && typeB instanceof CollectionType){
			CollectionType ca = (CollectionType)typeA;
			CollectionType cb = (CollectionType)typeB;
			
			if(comformTo(ca.getElementType(),cb.getElementType())){
				if(ca.getClass()==cb.getClass())
					return true;
				if(cb.getClass()==CollectionType.class)
					return true;
			}
			else
				return false;
		}
		//integer to real
		if(typeA==TypesPackage.eINSTANCE.getIntegerType()&&typeB==TypesPackage.eINSTANCE.getRealType())
			return true;
		else if(typeA==TypesPackage.eINSTANCE.getUnlimitedNatural()&&typeB==TypesPackage.eINSTANCE.getIntegerType())
			return true;
		
		if(typeA instanceof TupleType && typeB instanceof TupleType){
			TupleType ta = (TupleType)typeA,tb = (TupleType)typeB;
			for(EAttribute eb : tb.getProperties()){
				EClassifier et = ta.getKeyType(eb.getName());
				if(et==null)
					return false;
				if(comformTo(et,eb.getEType())==false)
					return false;
			}
			return true;
		}
		
		if(typeA instanceof EClass && typeB instanceof EClass){
			EClass ea = (EClass)typeA, eb = (EClass)typeB;
			return eb.isSuperTypeOf(ea);
		}
		
		return false;
	}
	
	static public boolean oclIsKindOf(Object self,Object type){
		if(type instanceof EClassifier)
			return oclType(self)==type;
		else if(type instanceof String){
			EClassifier st = oclType(self);
			if(st instanceof EClass) {
				
			} else {
				return st.getName().equals(type);
			}
		}
		return false;
	}
	static public boolean oclIsTypeOf(Object self,Object type){
		if(type instanceof EClassifier)
			return comformTo(oclType(self),(EClassifier)type);
		else  if(type instanceof String){
			EClassifier st = oclType(self);
			if(st instanceof EClass) {
				if(st.getName().equals(type))
					return true;
				else {
					for(EClass t : ((EClass) st).getEAllSuperTypes()){
						if(t.getName().equals(type))
							return true;
					}
					return false;
				}
			} else {
				return st.getName().equals(type);
			}
		}
		return false;
	}
	
	static public boolean oclIsNew(Object self){
		return false;
	}
	
	static public boolean oclIsNew(Object self,Object timestamp){
		return false;
	}
	
	static public Object add(Object self, Object r){
//		int ia = preTest(self,r,Double.class,Integer.class);
//		int ib = preTest(self,r,String.class);
		
//		if( ia==2 || ib ==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)+((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)+((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)+((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)+((Double)r);
		} else if(self instanceof String && r instanceof String){
			return ((String)self)+((String)r);
		}
		
		return OCLConstant.OCLInvalid;
	}
	static public Object minus(Object self, Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,OCLCollection.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)-((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)-((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)-((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)-((Double)r);
		} else if(self instanceof OCLCollection && r instanceof OCLCollection){
			OCLCollection col = new OCLBasicCollection(((OCLCollection)self).getElementOclType(),OCLCollection.CollectionTypeEnum.Set);
			for(Object e : (OCLCollection)self){
				if(((OCLCollection)r).contains(e)==false)
					col.add(e);
			}
			return col;
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object times(Object self, Object r){
//		int ia = preTest(self,r,Double.class,Integer.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)*((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)*((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)*((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)*((Double)r);
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object divide(Object self, Object r){
//		int ia = preTest(self,r,Double.class,Integer.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(r instanceof Double && ((Double)r)==0)
			return OCLConstant.OCLInvalid;
		else if(r instanceof Integer && ((Integer)r)==0)
			return OCLConstant.OCLInvalid;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)/((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)/((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)/((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)/((Double)r);
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object neg(Object self){
//		int ia = preTest(self);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double) {
			return -((Double)self);
		} else if(self instanceof Integer){
			return -((Integer)self);
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object abs(Object self){
//		int ia = preTest(self);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double) {
			return Math.abs((Double)self);
		} else if(self instanceof Integer){
			return Math.abs((Integer)self);
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object floor(Object self){
//		int ia = preTest(self);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double)
			return (int)Math.floor((Double)self);
		else if(self instanceof Integer)
			return (Integer)self;
		else return OCLConstant.OCLInvalid;
	}
	
	static public Object round(Object self){
//		int ia = preTest(self);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double)
			return (int)Math.round((Double)self);
		else if(self instanceof Integer)
			return (Integer)self;
		else return OCLConstant.OCLInvalid;
	}
	
	static public Object max(Object self,Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,UnlimitedNatural.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return Math.max(((Double)self),((Double)r));
		} else if(self instanceof Integer && r instanceof Integer){
			return Math.max(((Integer)self),((Integer)r));
		} else if(self instanceof Double && r instanceof Integer){
			return Math.max(((Double)self),((Integer)r));
		} else if(self instanceof Integer && r instanceof Double){
			return Math.max(((Integer)self),((Double)r));
		} else if(self instanceof UnlimitedNatural || r instanceof UnlimitedNatural)
			return OCLConstant.OCLUnlimitedNatural;
		return OCLConstant.OCLInvalid;
	}
	
	static public Object min(Object self,Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,UnlimitedNatural.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return Math.min(((Double)self),((Double)r));
		} else if(self instanceof Integer && r instanceof Integer){
			return Math.min(((Integer)self),((Integer)r));
		} else if(self instanceof Double && r instanceof Integer){
			return Math.min(((Double)self),((Integer)r));
		} else if(self instanceof Integer && r instanceof Double){
			return Math.min(((Integer)self),((Double)r));
		} else if(self instanceof UnlimitedNatural)
			return r;
		 else if(r instanceof UnlimitedNatural)
				return self;
		return OCLConstant.OCLInvalid;
	}
	
	static public Object less(Object self,Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,UnlimitedNatural.class,String.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)<((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)<((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)<((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)<((Double)r);
		} else if(self instanceof String && r instanceof String){
			return self.toString().compareTo(r.toString())<0;
		} else if(self == OCLConstant.OCLUnlimitedNatural)
			return false;
		 else if(r == OCLConstant.OCLUnlimitedNatural)
				return true;
		return OCLConstant.OCLInvalid;
	}
	static public Object greater(Object self,Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,String.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)>((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)>((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)>((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)>((Double)r);
		} else if(self instanceof String && r instanceof String){
			return self.toString().compareTo(r.toString())>0;
		} else if(self == OCLConstant.OCLUnlimitedNatural)
			return true;
		 else if(r == OCLConstant.OCLUnlimitedNatural)
				return false;
		return OCLConstant.OCLInvalid;
	}
	
	static public Object equal(Object self,Object r){
		if(self==null) self = OCLConstant.OCLNull;
		if(r==null) r = OCLConstant.OCLNull;
		
		if((self==OCLConstant.OCLInvalid&&r!=OCLConstant.OCLInvalid)||
				(self!=OCLConstant.OCLInvalid&&r==OCLConstant.OCLInvalid))
			return OCLConstant.OCLInvalid;
		
//		if((self==OCLConstant.OCLUninited && r!=OCLConstant.OCLUninited)
//				||(self!=OCLConstant.OCLUninited && r==OCLConstant.OCLUninited))
//			return OCLConstant.OCLUninited;

		if(self==r||self.equals(r))
			return true;
		return false;
	}
	
	static public Object not_equal(Object self, Object r){
		Object res = equal(self,r);
		
		if(res instanceof Boolean)
			return !((Boolean)res);
		return res;
	}
	
	static public Object less_or_equal(Object self,Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,String.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)<=((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)<=((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)<=((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)<=((Double)r);
		} else if(self instanceof String && r instanceof String){
			return self.toString().compareTo(r.toString())<=0;
		}  else if(r == OCLConstant.OCLUnlimitedNatural)
			return true;
		 else if(self == OCLConstant.OCLUnlimitedNatural)
				return false;
		return OCLConstant.OCLInvalid;
	}
	
	static public Object greater_or_equal(Object self,Object r){
//		int ia = preTest(self,r,Double.class,Integer.class,String.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Double && r instanceof Double){
			return ((Double)self)>=((Double)r);
		} else if(self instanceof Integer && r instanceof Integer){
			return ((Integer)self)>=((Integer)r);
		} else if(self instanceof Double && r instanceof Integer){
			return ((Double)self)>=((Integer)r);
		} else if(self instanceof Integer && r instanceof Double){
			return ((Integer)self)>=((Double)r);
		} else if(self instanceof String && r instanceof String){
			return self.toString().compareTo(r.toString())>=0;
		}  else if(self == OCLConstant.OCLUnlimitedNatural)
			return true;
		 else if(r == OCLConstant.OCLUnlimitedNatural)
				return false;
		
		return OCLConstant.OCLInvalid;
	}
	
	static public String toString(Object self){
		return self.toString();
	}
	
	static public Object div(Object self, Object r){
		Integer a,b;
		
//		int ia = preTest(self,r,Double.class,Integer.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Integer)
			a = (Integer)self;
		else if(self instanceof Double)
			a = ((Double)self).intValue();
		else return OCLConstant.OCLInvalid;
		
		if(r instanceof Integer)
			b = (Integer)r;
		else if(r instanceof Double)
			b = ((Double)r).intValue();
		else return OCLConstant.OCLInvalid;
		
		double result = a/b;
		if(result>0)
			return floor(result);
		else
			return -(Integer)floor(-result);
	}
	
	static public Object mod(Object self, Object r){
		Integer a,b;
		
//		int ia = preTest(self,r,Double.class,Integer.class);
//		if(ia==1)
//			return OCLConstant.OCLInvalid;
//		else if(ia==2)
//			return OCLConstant.OCLUninited;
		
		if(self instanceof Integer)
			a = (Integer)self;
		else if(self instanceof Double)
			a = ((Double)self).intValue();
		else return OCLConstant.OCLInvalid;
		
		if(r instanceof Integer)
			b = (Integer)r;
		else if(r instanceof Double)
			b = ((Double)r).intValue();
		else return OCLConstant.OCLInvalid;
		
		Object result = div(a,b);
		
		if(result==OCLConstant.OCLInvalid)
			return OCLConstant.OCLInvalid;
		else
			return a - ((Integer)result)*b;
	}
	
	static public Object size(Object self){
		if(self instanceof String && self != null){
			return ((String)self).length();
		} else if(self instanceof OCLCollection){
			return ((OCLCollection)self).size();
		} /*else if(self==OCLConstant.OCLUninited)
			return OCLConstant.OCLUninited;*/
		return OCLConstant.OCLInvalid;
	}
	
	static public Object concat(Object self,Object r){
		if(self==null||r==null)
			return OCLConstant.OCLInvalid;
		if(oclIsUndefined(self)||oclIsUndefined(r))
			return OCLConstant.OCLInvalid;
//		if(oclIsUninited(self)||oclIsUninited(r))
//			return OCLConstant.OCLUninited;
		
		return self.toString()+r.toString();
	}
	
	static public Object toInteger(Object self){
		if(self == null)
			return OCLConstant.OCLInvalid;
		if(self instanceof Integer)
			return self;
		else if(self instanceof Double)
			return ((Double)self).intValue();
		else if(self instanceof String)
			return Integer.parseInt((String)self);
//		else if(self==OCLConstant.OCLUninited)
//			return OCLConstant.OCLUninited;
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object toReal(Object self){
		if(self == null)
			return OCLConstant.OCLInvalid;
		if(self instanceof Integer)
			return ((Integer)self).doubleValue();
		else if(self instanceof Double)
			return self;
		else if(self instanceof String)
			return Double.parseDouble((String)self);
//		else if(self==OCLConstant.OCLUninited)
//			return OCLConstant.OCLUninited;
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object substring(Object self,Object l,Object u){
		if(oclIsUndefined(self))
			return OCLConstant.OCLInvalid;
		l = toInteger(l);
		u = toInteger(u);
		
		int ip = preTest(l,u,Integer.class);
		
		if(ip==0) {
//			if(self==OCLConstant.OCLUninited)
//				return OCLConstant.OCLUninited;
			//else 
				return self.toString().subSequence((Integer)l, (Integer)u);
		} else if(ip==1){
			return OCLConstant.OCLInvalid;
		} else {
			//return OCLConstant.OCLUninited;
			return OCLConstant.OCLInvalid;
		}
	}
	
	static public Object toUpperCase(Object self){
//		int ir = preTest(self);
//		if(ir==1)
//			return OCLConstant.OCLInvalid;
//		else if(ir==2){
//			return OCLConstant.OCLUninited;
//		}
		
		if(self instanceof String){
			return ((String)self).toUpperCase();
		} else return OCLConstant.OCLInvalid;
	}
	
	static public Object toLowerCase(Object self){
//		int ir = preTest(self);
//		if(ir==1)
//			return OCLConstant.OCLInvalid;
//		else if(ir==2){
//			return OCLConstant.OCLUninited;
//		}
		
		if(self instanceof String){
			return ((String)self).toLowerCase();
		} else return OCLConstant.OCLInvalid;
	}
	
	static public Object indexOf(Object self,Object s){
		if(self instanceof OCLCollection){
			return ((OCLCollection) self).indexOf(s);
		}
		
		int ir = preTest(self,s,String.class);
		if(ir==1)
			return OCLConstant.OCLInvalid;
//		else if(ir==2)
//			return OCLConstant.OCLUninited;
		
		return self.toString().indexOf(s.toString());
	}
	
	static public Object equalsIgnoreCase(Object self,Object s){
		int ir = preTest(self,s,String.class);
		if(ir==1)
			return OCLConstant.OCLInvalid;
//		else if(ir==2)
//			return OCLConstant.OCLUninited;
		
		return self.toString().equalsIgnoreCase(s.toString());
	}
	
	static public Object at(Object self,Object i){
//		int ir = preTest(self,i,String.class,Integer.class,OCLCollection.class);
//		if(ir==1)
//			return OCLConstant.OCLInvalid;
//		else if(ir==2)
//			return OCLConstant.OCLUninited;
		
		try {
			if(self instanceof String && i instanceof Integer){
				String s = ((Character)self.toString().charAt((Integer)i)).toString();
				return s;
			} else if(self instanceof OCLCollection && i instanceof Integer) {
				return ((OCLCollection)self).get((Integer)i);
			} else
				return OCLConstant.OCLInvalid;
		} catch (Exception e) {
			return OCLConstant.OCLInvalid;
		}
	}
	
	static public Object characters(Object self){
//		int ir = preTest(self);
//		if(ir==1)
//			return OCLConstant.OCLInvalid;
//		else if(ir==2){
//			return OCLConstant.OCLUninited;
//		}
		
		if(self instanceof String){
			char[] cs = ((String)self).toCharArray();
			OCLCollection collection = new OCLBasicCollection(TypesPackage.eINSTANCE.getStringType(),CollectionTypeEnum.Sequence,cs.length);
			for(char c : cs){
				collection.add(((Character)c).toString());
			}
			return collection;
		} else return OCLConstant.OCLInvalid;
	}
	
	static public Object toBoolean(Object self){
//		int ir = preTest(self);
//		if(ir==1)
//			return OCLConstant.OCLInvalid;
//		else if(ir==2){
//			return OCLConstant.OCLUninited;
//		}
		
		if(self instanceof String){
			return self.toString().equals("true");
		} else return OCLConstant.OCLInvalid;
	}
	
	static public Object not(Object self){
		if(self instanceof Boolean)
			return !((Boolean)self);
		else if(oclIsUndefined(self))
			return OCLConstant.OCLInvalid;
//		else if(oclIsUninited(self))
//			return OCLConstant.OCLUninited;
		return OCLConstant.OCLInvalid;
	}
	
	static public Object implies(Object self,Object r){
		if(isFalse(self))
			return true;
		else if(isTrue(self))
			return r;
		else
			return OCLConstant.OCLInvalid;
	}
	
	static public Object and(Object self,Object r){
		if(isTrue(self)&&isTrue(r))
			return true;
		else if(isFalse(self)||isFalse(r))
			return false;
//		else if(oclIsUninited(self)||oclIsUninited(r))
//			return OCLConstant.OCLUninited;
		else 
			return OCLConstant.OCLInvalid;
	}
	static public Object or(Object self,Object r){
		if(isTrue(self)||isTrue(r))
			return true;
		else if(isFalse(self)&&isFalse(r))
			return false;
//		else if(oclIsUninited(self)||oclIsUninited(r))
//			return OCLConstant.OCLUninited;
		else 
			return OCLConstant.OCLInvalid;
	}
	
	
	static public Object count(Object self,Object r){
		if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			int i=0;
			for(Object e : col){
				if(e.equals(r))
					i++;
			}
			return i;
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object includes(Object self,Object r){
		Object re = count(self,r);
		if(re instanceof Integer)
			if(((Integer)re)>0)
				return true;
			else return false;
		else return re;
	}
	
	static public Object excludes(Object self,Object r){
		return not(includes(self,r));
	}
	
	static public Object includesAll(Object self,Object r){
		
		if(r instanceof OCLCollection){
			OCLCollection rc = (OCLCollection)r;
			for(Object e : rc){
				Object ri = includes(self,e);
				if(ri instanceof Boolean){
					if(((Boolean)ri)==false)
						return false;					
				} else return ri;
			}
			return true;
		} else {
			Object re = count(self,r);
			if(re instanceof Integer)
				if(((Integer)re)>0)
					return true;
				else return false;
			else return re;
		}		
	}
	
	static public Object excludesAll(Object self,Object r){
		Object re = includesAll(self,r);
		return not(re);
	}
	
	static public Object isEmpty(Object self){
//		int i = preTest(self);
//		if(i==1)
//			return OCLConstant.OCLInvalid;
//		else if(i==2)
//			return OCLConstant.OCLInvalid;
//		else 
			if(self instanceof OCLCollection){
			OCLCollection cs = (OCLCollection)self;
			return cs.isEmpty();
		} else return OCLConstant.OCLInvalid;
	}
	
	static public Object notEmpty(Object self){
		return not(isEmpty(self));
	}
	
	static public Object product(Object self,Object r){
		if(oclIsUndefined(self)||oclIsUndefined(r))
			return OCLConstant.OCLInvalid;
		
//		if(oclIsUninited(self)||oclIsUninited(r))
//			return OCLConstant.OCLUninited;
		
//		Object temp = null;
		
		
		if(self instanceof OCLCollection){
			OCLCollection cs = (OCLCollection) self;
			
			if(r instanceof OCLCollection){
				OCLCollection cr = (OCLCollection) r;
				
				String[] keyNames = PRODUCETUPLESTRINGS;
				EClassifier[] valueOclTypes = new EClassifier[]{cs.getElementOclType(),cr.getElementOclType()};
				
				TupleType tt = (TupleType) TypesFactory.eINSTANCE.createTupleType(keyNames, valueOclTypes);
				
				OCLCollection col = new OCLBasicCollection(tt,OCLCollection.CollectionTypeEnum.Set);
				
				for(Object es : cs)
					for(Object er : cr){
						OCLTuple tuple = new OCLTuple(keyNames, valueOclTypes);
						tuple.put(keyNames[0], es);
						tuple.put(keyNames[1], er);
						col.add(tuple);
					}
				
				return col;
			} else {
				
				String[] keyNames = PRODUCETUPLESTRINGS;
				EClassifier[] valueOclTypes = new EClassifier[]{cs.getElementOclType(),oclType(r)};
				
				TupleType tt = (TupleType) TypesFactory.eINSTANCE.createTupleType(keyNames, valueOclTypes);
				
			    OCLCollection col = new OCLBasicCollection(tt,OCLCollection.CollectionTypeEnum.Set);
				
				for(Object es : cs) {
					OCLTuple tuple = new OCLTuple(keyNames, valueOclTypes);
					tuple.put(keyNames[0], es);
					tuple.put(keyNames[1], r);
					col.add(tuple);
				}
				
				return col;
			}
		} else {
			if(r instanceof OCLCollection){
				OCLCollection cr = (OCLCollection) r;
				
				String[] keyNames = PRODUCETUPLESTRINGS;
				EClassifier[] valueOclTypes = new EClassifier[]{oclType(self),cr.getElementOclType()};
				
				TupleType tt = (TupleType) TypesFactory.eINSTANCE.createTupleType(keyNames, valueOclTypes);
				OCLCollection col = new OCLBasicCollection(tt,OCLCollection.CollectionTypeEnum.Set);
				
				for(Object er : cr){
					OCLTuple tuple = new OCLTuple(keyNames, valueOclTypes);
					tuple.put(keyNames[0], self);
					tuple.put(keyNames[1], er);
					col.add(tuple);
				}
				
				return col;
			} else {
				String[] keyNames = PRODUCETUPLESTRINGS;
				EClassifier[] valueOclTypes = new EClassifier[]{oclType(self),oclType(r)};
				OCLTuple tuple = new OCLTuple(keyNames, valueOclTypes);
				tuple.put(keyNames[0], self);
				tuple.put(keyNames[1], r);
				return tuple;
			}
		}
	}
	
	static public Object asSet(Object self){
//		int is = preTest(self);
//		if(is==-1) return OCLConstant.OCLInvalid;
//		else if(is==-2) return OCLConstant.OCLUninited;
		
		if(self==OCLConstant.OCLInvalid)
			return OCLConstant.OCLInvalid;
		
		if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			OCLCollection result = new OCLBasicCollection(col.getElementOclType(),OCLCollection.CollectionTypeEnum.Set,col.size());
			result.addAll(col);
			return result;
		} else {
			OCLCollection result = new OCLBasicCollection(oclType(self),OCLCollection.CollectionTypeEnum.Set);
			result.add(self);
			return result;
		}
	}
	
	static public Object asBag(Object self){
//		int is = preTest(self);
//		if(is==-1) return OCLConstant.OCLInvalid;
//		else if(is==-2) return OCLConstant.OCLUninited;
		
		if(self==OCLConstant.OCLInvalid)
			return OCLConstant.OCLInvalid;
		
		if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			OCLCollection result = new OCLBasicCollection(col.getElementOclType(),OCLCollection.CollectionTypeEnum.Bag,col.size());
			result.addAll(col);
			return result;
		} else {
			OCLCollection result = new OCLBasicCollection(oclType(self),OCLCollection.CollectionTypeEnum.Bag);
			result.add(self);
			return result;
		}
	}
	
	static public Object asSequence(Object self){
//		int is = preTest(self);
//		if(is==-1) return OCLConstant.OCLInvalid;
//		else if(is==-2) return OCLConstant.OCLUninited;
		
		if(self==OCLConstant.OCLInvalid)
			return OCLConstant.OCLInvalid;
		
		if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			OCLCollection result = new OCLBasicCollection(col.getElementOclType(),OCLCollection.CollectionTypeEnum.Sequence,col.size());
			result.addAll(col);
			return result;
		} else {
			OCLCollection result = new OCLBasicCollection(oclType(self),OCLCollection.CollectionTypeEnum.Sequence);
			result.add(self);
			return result;
		}
	}
	static public Object asOrderedSet(Object self){
//		int is = preTest(self);
//		if(is==-1) return OCLConstant.OCLInvalid;
//		else if(is==-2) return OCLConstant.OCLUninited;
		
		if(self==OCLConstant.OCLInvalid)
			return OCLConstant.OCLInvalid;
		
		if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			OCLCollection result = new OCLBasicCollection(col.getElementOclType(),OCLCollection.CollectionTypeEnum.OrderedSet,col.size());
			result.addAll(col);
			return result;
		} else {
			OCLCollection result = new OCLBasicCollection(oclType(self),OCLCollection.CollectionTypeEnum.OrderedSet);
			result.add(self);
			return result;
		}
	}
	
	static public Object flatten(Object self){
		OCLCollection result = null;
		
		if(self instanceof OCLCollection)
			result = new OCLBasicCollection(TypesPackage.eINSTANCE.getOclAny(),((OCLCollection) self).getCollectionType());
		else
			result = new OCLBasicCollection(TypesPackage.eINSTANCE.getOclAny(),OCLCollection.CollectionTypeEnum.Collection);
		
		if(flatten(self,result))
			return result;
		else return OCLConstant.OCLInvalid;
	}
	
	static private boolean flatten(Object self,OCLCollection result){
		int ir = preTest(self);
		if(ir!=0)
			return false;
		
		if(self instanceof OCLCollection){
			OCLCollection col = (OCLCollection)self;
			for(Object e : col){
				if(flatten(e,result)==false)
					return false;
			}
			return true;
		} else {
			result.add(self);
			return true;
		}
	}
	
	static public Object union(Object self,Object r){
		int ia = preTest(self,r,OCLCollection.class);
		if(ia!=0)
			return OCLConstant.OCLInvalid;
		
		OCLCollection cs = (OCLCollection)self;
		OCLCollection cr = (OCLCollection)r;
		
		if(comformTo(cr.getElementOclType(),cs.getElementOclType())==false)
			return OCLConstant.OCLInvalid;
		
		OCLCollection result = null;
		
		if(cs.getCollectionType()==CollectionTypeEnum.Set&&cr.getCollectionType()==CollectionTypeEnum.Set){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Set);
			
			result.addAll(cs);
			result.addAll(cr);
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Set&&cr.getCollectionType()==CollectionTypeEnum.Bag){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Bag);
			
			result.addAll(cs);
			result.addAll(cr);
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Bag&&cr.getCollectionType()==CollectionTypeEnum.Set){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Bag);
			
			result.addAll(cs);
			result.addAll(cr);
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Bag&&cr.getCollectionType()==CollectionTypeEnum.Bag){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Bag);
			
			result.addAll(cs);
			result.addAll(cr);
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Sequence&&cr.getCollectionType()==CollectionTypeEnum.Sequence){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Bag);
			
			result.addAll(cs);
			result.addAll(cr);
		} else {
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Collection);
			
			result.addAll(cs);
			result.addAll(cr);
		}
		
		return result;
		
	}
	
	static public Object intersection(Object self, Object r){
		int ir = preTest(self,r,OCLCollection.class);
		if(ir!=0)
			return OCLConstant.OCLInvalid;
		
		OCLCollection cs = (OCLCollection)self;
		OCLCollection cr = (OCLCollection)r;
		
		if(cs.getElementOclType()!=cr.getElementOclType())
			return OCLConstant.OCLInvalid;
		
		
		OCLCollection result = null;
		if(cs.getCollectionType()==CollectionTypeEnum.Set&&cr.getCollectionType()==CollectionTypeEnum.Set){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Set);
			for(Object e : cs){
				if(cr.contains(e))
					result.add(e);
			}
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Set&&cr.getCollectionType()==CollectionTypeEnum.Bag){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Set);
			for(Object e : cs){
				if(cr.contains(e))
					result.add(e);
			}
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Bag&&cr.getCollectionType()==CollectionTypeEnum.Bag){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Bag);
			for(Object e : cs){
				int ia = (Integer)count(result, e);
				int ib = (Integer)count(self,e);
				int ic = (Integer)count(r,e);
				
				if(ia<Math.min(ib, ic)){
					result.add(e);
				}
			}
			
		} else if(cs.getCollectionType()==CollectionTypeEnum.Bag&&cr.getCollectionType()==CollectionTypeEnum.Set){
			result = new OCLBasicCollection(cs.getElementOclType(),CollectionTypeEnum.Set);
			for(Object e : cr){
				if(cs.contains(e))
					result.add(e);
			}
		}
		if(result==null)
			return OCLConstant.OCLInvalid;
		else
			return result;
	}
	
	static public Object including(Object self, Object r){
		if(self instanceof OCLCollection && self != null){
			OCLCollection result = new OCLBasicCollection(((OCLCollection)self).getElementOclType(),((OCLCollection)self).getCollectionType(),((OCLCollection)self).size()+1);
			result.addAll((OCLCollection)self);
			result.add(r);
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object excluding(Object self, Object r){
		if(self instanceof OCLCollection && self != null){
			OCLCollection result = new OCLBasicCollection(((OCLCollection)self).getElementOclType(),((OCLCollection)self).getCollectionType(),((OCLCollection)self).size());
			result.addAll((OCLCollection)self);
			result.removeAll(r);
		}
		return OCLConstant.OCLInvalid;
	}
	
	static public Object symmetricDifference(Object self,Object r){
		int ir = preTest(self,r,OCLCollection.class);
		if(ir!=0)
			return OCLConstant.OCLInvalid;
		
		OCLCollection result =  new OCLBasicCollection(((OCLCollection)self).getElementOclType(),OCLCollection.CollectionTypeEnum.Set,((OCLCollection)self).size()+((OCLCollection)r).size());
		for(Object e : (OCLCollection)self){
			if(((OCLCollection)r).contains(e)==false)
				result.add(e);
		}
		for(Object e : (OCLCollection)r){
			if(((OCLCollection)self).contains(e)==false)
				result.add(e);
		}
		return result;
	}
	
	static public Object prepend(Object self,Object r){
		if(self instanceof OCLCollection){
			if(comformTo(oclType(r), ((OCLCollection) self).getElementOclType())==false)
				return OCLConstant.OCLInvalid;
			
			OCLCollection result = new OCLBasicCollection(((OCLCollection) self).getElementOclType(),((OCLCollection) self).getCollectionType(),((OCLCollection) self).size()+1);
			result.addAll((OCLCollection) self);
			result.add(0, r);
			return result;
		}
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object append(Object self,Object r){
		if(self instanceof OCLCollection){
			if(comformTo(oclType(r), ((OCLCollection) self).getElementOclType())==false)
				return OCLConstant.OCLInvalid;
			
			OCLCollection result = new OCLBasicCollection(((OCLCollection) self).getElementOclType(),((OCLCollection) self).getCollectionType(),((OCLCollection) self).size()+1);
			result.addAll((OCLCollection) self);
			result.add(r);
			return result;
		}
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object insertAt(Object self,Object r,Object e){
		if(self instanceof OCLCollection&&r instanceof Integer){
			if(comformTo(oclType(r), ((OCLCollection) self).getElementOclType())==false)
				return OCLConstant.OCLInvalid;
			
			OCLCollection result = new OCLBasicCollection(((OCLCollection) self).getElementOclType(),((OCLCollection) self).getCollectionType(),((OCLCollection) self).size()+1);
			result.addAll((OCLCollection) self);
			result.add((Integer)r,e);
			return result;
		}
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object first(Object self){
		if(self instanceof OCLCollection)
			return at(self,0);
		return self;
	}
	
	static public Object last(Object self){
		if(self instanceof OCLCollection)
			return at(self,((OCLCollection) self).size()-1);
		return self;
	}
	
	static public Object subList(Object self, Object l, Object u){
		if(self instanceof OCLCollection && l instanceof Integer && u instanceof Integer) {
			OCLCollection col = new OCLBasicCollection(((OCLCollection)self).getElementOclType(),((OCLCollection)self).getCollectionType(),(Integer)u-(Integer)l);
			for(int i = (Integer)l;i<=(Integer)u;i++){
				col.add(((OCLCollection)self).get(i));
			}
			return col;
		}
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object subOrderedSet(Object self, Object l, Object u){
		if(self instanceof OCLCollection){
			if(((OCLCollection) self).getCollectionType()==OCLCollection.CollectionTypeEnum.OrderedSet)
				return subList(self,l,u);
		}
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object subSequence(Object self, Object l, Object u){
		if(self instanceof OCLCollection){
			if(((OCLCollection) self).getCollectionType()==OCLCollection.CollectionTypeEnum.Sequence)
				return subList(self,l,u);
		}
		
		return OCLConstant.OCLInvalid;
	}
	
	static public Object reverse(Object self){
		if(self instanceof OCLCollection){
			OCLCollection col = new OCLBasicCollection(((OCLCollection) self).getElementOclType(),((OCLCollection) self).getCollectionType(),((OCLCollection) self).size());
			for(int i = col.size()-1;i>=0;i--){
				col.add(((OCLCollection) self).get(i));
			}
			return col;
		}
		return self;
	}
	public static Object wrap(Object result) {
		if(result==null)
			return OCLConstant.OCLNull;
		else if(result instanceof EcoreEList){
			return new OCLEcoreCollection((EcoreEList<?>)result);
		} 
//		else if(result instanceof EcoreEList.UnmodifiableEList){
//			return new OCLUnmodifiableEcoreCollection((EcoreEList.UnmodifiableEList<?>)result);
//		} 
		else if(result instanceof OCLCollection)
			return result;
		else if(result instanceof List){
			OCLCollection col = new OCLBasicCollection(TypesPackage.eINSTANCE.getOclAny(),CollectionTypeEnum.Collection,((List<?>) result).size());
			col.addAll((List<?>)result);
			return col;
		} else if(result instanceof Character){
			return result.toString();
		} else if(result instanceof Float)
			return ((Float)result).doubleValue();
		return result;
	}
	
	public static Object castValue(Object object, EClassifier type) {
		EClassifier cls = oclType(object);
		if(cls == type)
			return object;
		
		try {
			if(cls == TypesPackage.eINSTANCE.getStringType()){
				if(type == TypesPackage.eINSTANCE.getIntegerType()){
					return Integer.parseInt(object.toString());
				} else if(type == TypesPackage.eINSTANCE.getRealType()){
					return Double.parseDouble(object.toString());
				} else if(type == TypesPackage.eINSTANCE.getBooleanType()){
					return Boolean.parseBoolean(object.toString());
				}
			} else if(cls == TypesPackage.eINSTANCE.getIntegerType()){
				if(type == TypesPackage.eINSTANCE.getStringType()){
					return object.toString();
				} else if(type == TypesPackage.eINSTANCE.getRealType()){
					return ((Integer)object).doubleValue();
				} else if(type == TypesPackage.eINSTANCE.getBooleanType()){
					return OCLConstant.OCLInvalid;
				}
			} else if(cls == TypesPackage.eINSTANCE.getRealType()){
				if(type == TypesPackage.eINSTANCE.getStringType()){
					return object.toString();
				} else if(type == TypesPackage.eINSTANCE.getIntegerType()){
					return ((Double)object).intValue();
				} else if(type == TypesPackage.eINSTANCE.getBooleanType()){
					return OCLConstant.OCLInvalid;
				}
			} else if(cls == TypesPackage.eINSTANCE.getBooleanType()){
				if(type == TypesPackage.eINSTANCE.getStringType()){
					return object.toString();
				} else if(type == TypesPackage.eINSTANCE.getIntegerType()){
					return OCLConstant.OCLInvalid;
				} else if(type == TypesPackage.eINSTANCE.getRealType()){
					return OCLConstant.OCLInvalid;
				}
			}
		} catch (NumberFormatException e) {
			return OCLConstant.OCLInvalid;
		}
		
		return object;
	}
	
	public static Object unwrap(Object result) {
		if(result==OCLConstant.OCLNull||result==OCLConstant.OCLInvalid)
			return null;
		else if(result==OCLConstant.OCLUnlimitedNatural){
			return -1;
		}
		else if(result instanceof OCLEcoreCollection){
			return ((OCLEcoreCollection) result).content;
		} 
		else if(result instanceof OCLUnmodifiableEcoreCollection){
			return ((OCLUnmodifiableEcoreCollection) result).content;
		} 
		else if(result instanceof OCLCollection)
			return result;
		return result;
	}
	public static boolean checkComplexType(EClassifier v){
		if(v.getEPackage()==TypesPackage.eINSTANCE){
			return false;
		}else if(v instanceof TupleType){
			return false;
		} else if(v instanceof CollectionType){
			return false;
		}
		return true;
	}
}
